/*
* Copyright (c) 2014 Razeware LLC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/

import UIKit
import CoreData

class JournalListViewController:
  UITableViewController, NSFetchedResultsControllerDelegate,
    JournalEntryDelegate {

  var coreDataStack: CoreDataStack!
  lazy var fetchedResultController:
    NSFetchedResultsController =
      self.surfJournalfetchedResultController()
  
  @IBOutlet weak var exportButton: UIBarButtonItem!

  
  // MARK: - View Lifecycle
  
  override func viewDidLoad() {
    super.viewDidLoad()
  }
  

  // MARK: - NSFetchedResultsController
  
  func surfJournalfetchedResultController()
    -> NSFetchedResultsController {
      
      fetchedResultController =
        NSFetchedResultsController(
          fetchRequest: surfJournalFetchRequest(),
          managedObjectContext: coreDataStack.context,
          sectionNameKeyPath: nil,
          cacheName: nil)
      
      fetchedResultController.delegate = self
      
      var error: NSError? = nil
      if (!fetchedResultController.performFetch(&error)){
        println("Error: \(error?.localizedDescription)")
        abort()
      }
      
      return fetchedResultController
  }
  
  func surfJournalFetchRequest() -> NSFetchRequest {
      
    let fetchRequest =
      NSFetchRequest(entityName: "JournalEntry")
    fetchRequest.fetchBatchSize = 20
      
    let sortDescriptor =
      NSSortDescriptor(key: "date", ascending: false)
      
    fetchRequest.sortDescriptors = [sortDescriptor]
      
    return fetchRequest
  }
  
  
  // MARK: - NSFetchedResultsControllerDelegate
  
  func controllerDidChangeContent(controller:
    NSFetchedResultsController) {
      tableView.reloadData()
  }


  // MARK: - UITableViewDataSource

  override func numberOfSectionsInTableView(
    tableView: UITableView) -> Int {

    return fetchedResultController.sections!.count
  }

  override func tableView(tableView: UITableView,
    numberOfRowsInSection section: Int) -> Int {

    return
      fetchedResultController.sections![section].numberOfObjects
  }

  override func tableView(tableView: UITableView,
    cellForRowAtIndexPath indexPath: NSIndexPath)
    -> UITableViewCell {

    let cell =
      tableView.dequeueReusableCellWithIdentifier("Cell",
        forIndexPath: indexPath) as! SurfEntryTableViewCell

      configureCell(cell, indexPath: indexPath)

    return cell
  }
  
  func configureCell(cell: SurfEntryTableViewCell,
    indexPath:NSIndexPath) {
    
    let surfJournalEntry =
      fetchedResultController.objectAtIndexPath(indexPath)
        as! JournalEntry
    
    cell.dateLabel.text = surfJournalEntry.stringForDate()
    
    if let rating = surfJournalEntry.rating?.intValue {
      switch rating {
      case 1:
        cell.starOneFilledImageView.hidden = false
        cell.starTwoFilledImageView.hidden = true
        cell.starThreeFilledImageView.hidden = true
        cell.starFourFilledImageView.hidden = true
        cell.starFiveFilledImageView.hidden = true
      case 2:
        cell.starOneFilledImageView.hidden = false
        cell.starTwoFilledImageView.hidden = false
        cell.starThreeFilledImageView.hidden = true
        cell.starFourFilledImageView.hidden = true
        cell.starFiveFilledImageView.hidden = true
      case 3:
        cell.starOneFilledImageView.hidden = false
        cell.starTwoFilledImageView.hidden = false
        cell.starThreeFilledImageView.hidden = false
        cell.starFourFilledImageView.hidden = true
        cell.starFiveFilledImageView.hidden = true
      case 4:
        cell.starOneFilledImageView.hidden = false
        cell.starTwoFilledImageView.hidden = false
        cell.starThreeFilledImageView.hidden = false
        cell.starFourFilledImageView.hidden = false
        cell.starFiveFilledImageView.hidden = true
      case 5:
        cell.starOneFilledImageView.hidden = false
        cell.starTwoFilledImageView.hidden = false
        cell.starThreeFilledImageView.hidden = false
        cell.starFourFilledImageView.hidden = false
        cell.starFiveFilledImageView.hidden = false
      default :
        cell.starOneFilledImageView.hidden = true
        cell.starTwoFilledImageView.hidden = true
        cell.starThreeFilledImageView.hidden = true
        cell.starFourFilledImageView.hidden = true
        cell.starFiveFilledImageView.hidden = true
      }
    }
  }

  override func tableView(tableView: UITableView,
  commitEditingStyle editingStyle: UITableViewCellEditingStyle,
    forRowAtIndexPath indexPath: NSIndexPath) {

    if editingStyle == .Delete {
      let surfJournalEntry =
        fetchedResultController.objectAtIndexPath(indexPath)
          as! JournalEntry
      coreDataStack.context.deleteObject(surfJournalEntry)
      coreDataStack.saveContext()
    }
  }
  
  override func tableView(tableView: UITableView,
    heightForRowAtIndexPath indexPath: NSIndexPath)
      -> CGFloat {
    return 44;
  }
  
  
  // MARK: - JournalEntryDelegate

  func didFinishViewController(
    viewController:JournalEntryViewController, didSave:Bool) {

    // 1
    if didSave {
      // 2
      var error: NSError? = nil
      let context = viewController.context
      context.performBlock({ () -> Void in
        if context.hasChanges && !context.save(&error) {
          println(
            "Couldn't save: \(error), \(error?.userInfo)")
          abort()
        }
        // 3
        self.coreDataStack.saveContext()
      })
    }
      
    // 4
    dismissViewControllerAnimated(true, completion: {})
  }

  
  // MARK: - Segues
  
  override func prepareForSegue(segue: UIStoryboardSegue,
    sender: AnyObject?) {
      
      // 1
      if segue.identifier == "SegueListToDetail" {
        
        // 2
        let indexPath = tableView.indexPathForSelectedRow()
        let surfJournalEntry =
          fetchedResultController.objectAtIndexPath(
            indexPath!) as! JournalEntry
        
        //3
        let navigationController =
          segue.destinationViewController as!
            UINavigationController
        let detailViewController =
          navigationController.topViewController as!
            JournalEntryViewController
        
        //4
        detailViewController.journalEntry = surfJournalEntry
        detailViewController.context
          = surfJournalEntry.managedObjectContext
        detailViewController.delegate = self
        
      } else if segue.identifier == "SegueListToDetailAdd" {
        
        let navigationController =
          segue.destinationViewController as!
            UINavigationController
        let detailViewController =
        navigationController.topViewController as!
        JournalEntryViewController
        
        
        let journalEntryEntity =
          NSEntityDescription.entityForName("JournalEntry",
            inManagedObjectContext: coreDataStack.context)
        let newJournalEntry =
          JournalEntry(entity: journalEntryEntity!,
            insertIntoManagedObjectContext:
              coreDataStack.context)
        
        detailViewController.journalEntry = newJournalEntry
        detailViewController.context
          = newJournalEntry.managedObjectContext
        detailViewController.delegate = self
      }
  }
  
  
  // MARK: - Target Action
  
  @IBAction func exportButtonTapped(sender: AnyObject) {
    exportCSVFile()
  }
  

  // MARK: - Export
  
  func activityIndicatorBarButtonItem() -> UIBarButtonItem {
    let activityIndicator =
      UIActivityIndicatorView(activityIndicatorStyle:
        UIActivityIndicatorViewStyle.Gray)
    let barButtonItem =
      UIBarButtonItem(customView: activityIndicator)
    activityIndicator.startAnimating()
    
    return barButtonItem
  }
  
  func exportBarButtonItem() -> UIBarButtonItem {
    
    return UIBarButtonItem(
      title: "Export", style: UIBarButtonItemStyle.Plain,
      target: self, action: "exportButtonTapped:")
  }
  
  func showExportFinishedAlertView(exportPath: String) {
    
    let message =
      "The exported CSV file can be found at \(exportPath)"
    let alertView = UIAlertView(
      title: "Export Finished", message: message,
      delegate: nil, cancelButtonTitle: "OK")
    
    alertView.show()
  }
  
  func exportCSVFile() {
    
    navigationItem.leftBarButtonItem =
      activityIndicatorBarButtonItem()
    
		/*
		1.1.First, you create a new managed object context called privateContext with a concurrency type of PrivateQueueConcurrencyType, which specifies that the context will be associated with a private dispatch queue. Once you’ve created the new context, you assign it the same persistent store coordinator as the main managed object context.
		1.2.Next, you call performBlock. This function asynchronously performs the given block on the context’s queue. In this case, the queue is private.
		1.3.Just as before, you retrieve all JournalEntry entities by executing a fetch request. But this time, you use the private context to execute the fetch request.
		2.The code creates the URL for the exported CSV file by appending the file name (“export.csv”) to the output of NSTemporaryDirectory. The path returned by NSTemporaryDirectory is a unique directory for temporary file storage. This a good place for files that can easily be generated again and don’t need to be backed up by iTunes or to iCloud. After creating the export URL, the code calls createFileAtPath(_:contents:attributes:) to create the empty file to store the exported data. If a file already exists at the specified file path, then the code removes it first.
		*/
    // 1.1
		/*
		There are three concurrency types a managed object context can use:
		
		ConfinementConcurrencyType specifies that the context will use the thread confinement pattern and that the developer will be responsible for managing all thread access. You should consider this type deprecated and never use it, as the next two types will cover all use cases.
		
		PrivateQueueConcurrencyType specifies that the context will be associated with a private dispatch queue instead of the main queue. This is the type of queue you just used to move the export operation off of the main queue so that it no longer interferes with the UI.
		
		MainQueueConcurrencyType, the default type, specifies that the context will be associated with the main queue. This type is what the main context (coreDataStack.context) uses. Any UI operation, such as creating the fetched results controller for the table view, must use a context of this type.
		*/
    let privateContext = NSManagedObjectContext(concurrencyType: .PrivateQueueConcurrencyType)
		privateContext.persistentStoreCoordinator = coreDataStack.context.persistentStoreCoordinator
		
		// 1.2
		privateContext.performBlock {
			() -> Void in
			// 1.3
			var fetchRequestError: NSError? = nil
			let results = privateContext.executeFetchRequest(self.surfJournalFetchRequest(), error: &fetchRequestError)
			if results == nil {
				println("ERROR: \(fetchRequestError)")
			}

			// 2
			let exportFilePath =
				NSTemporaryDirectory() + "export.csv"
			let exportFileURL =
				NSURL(fileURLWithPath: exportFilePath)!
			NSFileManager.defaultManager().createFileAtPath(
				exportFilePath, contents: NSData(), attributes: nil)
			
			/*
			3.First, the app needs to create a file handler for writing, which is simply an object that handles the low-level disk operations necessary for writing data. To create a file handler for writing, the code calls fileHandleForWritingToURL(_:error:).
			4.Using a for-in statement, the code iterates over all JournalEntry entities. During each iteration, the code creates a UTF8-encoded string using csv and dataUsingEncoding(_:allowLossyConversion:). It then writes the UTF8 string to disk using writeData.
			5.Finally, the code closes the export file-writing file handler, since it’s no longer needed.
			*/
			// 3
			var fileHandleError: NSError? = nil
			let fileHandle = NSFileHandle(forWritingToURL: exportFileURL,
				error: &fileHandleError)
			if let fileHandle = fileHandle {

				// 4
				for object in results! {
					let journalEntry = object as! JournalEntry
					
					fileHandle.seekToEndOfFile()
					let csvData = journalEntry.csv().dataUsingEncoding(
						NSUTF8StringEncoding, allowLossyConversion: false)
					fileHandle.writeData(csvData!)
				}
				
				// 5
				fileHandle.closeFile()
		
				println("Export Path: \(exportFilePath)")
				
				/*
				6.You should always perform all operations related to the UI, such as showing an alert view when the export operation is finished, on the main queue; otherwise unpredictable things will happen. You use the dispatch_async and dispatch_get_main_queue to show the final alert view message on the main queue.
				*/
				// 6
				dispatch_async(dispatch_get_main_queue()) {
					() -> Void in
					
					self.navigationItem.leftBarButtonItem = self.exportBarButtonItem()
					println("Export Path: \(exportFilePath)")
					
					self.showExportFinishedAlertView(exportFilePath)
				}
			} else {
				dispatch_async(dispatch_get_main_queue()) {
					() -> Void in
					
					self.navigationItem.leftBarButtonItem = self.exportBarButtonItem()
					println("ERROR: \(fileHandleError)")
				}
			}
		}
	}
}
